home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / Net / LMTP.php < prev    next >
PHP Script  |  2004-10-01  |  22KB  |  730 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>      |
  17. // |          Chuck Hagenbuch <chuck@horde.org>                           |
  18. // |          Jon Parise <jon@php.net>                                    |
  19. // |                                                                      |
  20. // +----------------------------------------------------------------------+
  21.  
  22. require_once 'Net/Socket.php';
  23.  
  24. /**
  25.  * Provides an implementation of the LMTP protocol using PEAR's
  26.  * Net_Socket:: class.
  27.  *
  28.  * @package Net_LMTP
  29.  * @author  Chuck Hagenbuch <chuck@horde.org>
  30.  * @author  Jon Parise <jon@php.net>
  31.  * @author  Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
  32.  */
  33. class Net_LMTP {
  34.  
  35.  
  36.     /**
  37.      * The server to connect to.
  38.      * @var string
  39.      */
  40.     var $_host;
  41.  
  42.     /**
  43.      * The port to connect to.
  44.      * @var int
  45.      */
  46.     var $_port;
  47.  
  48.     /**
  49.      * The value to give when sending LHLO.
  50.      * @var string
  51.      */
  52.     var $_localhost;
  53.  
  54.     /**
  55.      * Should debugging output be enabled?
  56.      * @var boolean
  57.      * @access private
  58.      */
  59.     var $_debug = false;
  60.  
  61.     /**
  62.      * The socket resource being used to connect to the LMTP server.
  63.      * @var resource
  64.      * @access private
  65.      */
  66.     var $_socket = null;
  67.  
  68.     /**
  69.      * The most recent server response code.
  70.      * @var int
  71.      * @access private
  72.      */
  73.     var $_code = -1;
  74.  
  75.     /**
  76.      * The most recent server response arguments.
  77.      * @var array
  78.      * @access private
  79.      */
  80.     var $_arguments = array();
  81.  
  82.     /**
  83.      * Stores detected features of the LMTP server.
  84.      * @var array
  85.      * @access private
  86.      */
  87.     var $_esmtp = array();
  88.  
  89.  
  90.    /**
  91.     * The auth methods this class support
  92.     * @var array
  93.     */
  94.     var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN');
  95.  
  96.  
  97.     /**
  98.     * The auth methods this class support
  99.     * @var array
  100.     */
  101.     var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');
  102.  
  103.  
  104.  
  105.  
  106.     /**
  107.      * Instantiates a new Net_LMTP object, overriding any defaults
  108.      * with parameters that are passed in.
  109.      *
  110.      * @param string The server to connect to.
  111.      * @param int The port to connect to.
  112.      * @param string The value to give when sending LHLO.
  113.      *
  114.      * @access  public
  115.      * @since   1.0
  116.      */
  117.     function Net_LMTP($host = 'localhost', $port = 2003, $localhost = 'localhost')
  118.     {
  119.         $this->_host = $host;
  120.         $this->_port = $port;
  121.         $this->_localhost = $localhost;
  122.         $this->_socket = new Net_Socket();
  123.  
  124.        if ((@include_once 'Auth/SASL.php') == false) {
  125.             foreach($this->supportedSASLAuthMethods as $SASLMethod){
  126.                 $pos = array_search( $SASLMethod , $this->supportedAuthMethods);
  127.                 unset($this->supportedAuthMethods[$pos]);
  128.             }
  129.         }
  130.  
  131.  
  132.     }
  133.  
  134.     /**
  135.      * Set the value of the debugging flag.
  136.      *
  137.      * @param   boolean $debug      New value for the debugging flag.
  138.      *
  139.      * @access  public
  140.      * @since   1.0
  141.      */
  142.     function setDebug($debug)
  143.     {
  144.         $this->_debug = $debug;
  145.     }
  146.  
  147.     /**
  148.      * Send the given string of data to the server.
  149.      *
  150.      * @param   string  $data       The string of data to send.
  151.      *
  152.      * @return  mixed   True on success or a PEAR_Error object on failure.
  153.      *
  154.      * @access  private
  155.      * @since   1.0
  156.      */
  157.     function _send($data)
  158.     {
  159.         if ($this->_debug) {
  160.             echo "DEBUG: Send: $data\n";
  161.         }
  162.  
  163.         if (PEAR::isError($error = $this->_socket->write($data))) {
  164.             return new PEAR_Error('Failed to write to socket: ' .
  165.                                   $error->getMessage());
  166.         }
  167.  
  168.         return true;
  169.     }
  170.  
  171.     /**
  172.      * Send a command to the server with an optional string of arguments.
  173.      * A carriage return / linefeed (CRLF) sequence will be appended to each
  174.      * command string before it is sent to the LMTP server.
  175.      *
  176.      * @param   string  $command    The LMTP command to send to the server.
  177.      * @param   string  $args       A string of optional arguments to append
  178.      *                              to the command.
  179.      *
  180.      * @return  mixed   The result of the _send() call.
  181.      *
  182.      * @access  private
  183.      * @since   1.0
  184.      */
  185.     function _put($command, $args = '')
  186.     {
  187.         if (!empty($args)) {
  188.             return $this->_send($command . ' ' . $args . "\r\n");
  189.         }
  190.  
  191.         return $this->_send($command . "\r\n");
  192.     }
  193.  
  194.     /**
  195.      * Read a reply from the LMTP server.  The reply consists of a response
  196.      * code and a response message.
  197.      *
  198.      * @param   mixed   $valid      The set of valid response codes.  These
  199.      *                              may be specified as an array of integer
  200.      *                              values or as a single integer value.
  201.      *
  202.      * @return  mixed   True if the server returned a valid response code or
  203.      *                  a PEAR_Error object is an error condition is reached.
  204.      *
  205.      * @access  private
  206.      * @since   1.0
  207.      *
  208.      * @see     getResponse
  209.      */
  210.     function _parseResponse($valid)
  211.     {
  212.         $this->_code = -1;
  213.         $this->_arguments = array();
  214.  
  215.         while ($line = $this->_socket->readLine()) {
  216.             if ($this->_debug) {
  217.                 echo "DEBUG: Recv: $line\n";
  218.             }
  219.  
  220.             /* If we receive an empty line, the connection has been closed. */
  221.             if (empty($line)) {
  222.                 $this->disconnect();
  223.                 return new PEAR_Error("Connection was unexpectedly closed");
  224.             }
  225.  
  226.             /* Read the code and store the rest in the arguments array. */
  227.             $code = substr($line, 0, 3);
  228.             $this->_arguments[] = trim(substr($line, 4));
  229.  
  230.             /* Check the syntax of the response code. */
  231.             if (is_numeric($code)) {
  232.                 $this->_code = (int)$code;
  233.             } else {
  234.                 $this->_code = -1;
  235.                 break;
  236.             }
  237.  
  238.             /* If this is not a multiline response, we're done. */
  239.             if (substr($line, 3, 1) != '-') {
  240.                 break;
  241.             }
  242.         }
  243.  
  244.         /* Compare the server's response code with the valid code. */
  245.         if (is_int($valid) && ($this->_code === $valid)) {
  246.             return true;
  247.         }
  248.  
  249.         /* If we were given an array of valid response codes, check each one. */
  250.         if (is_array($valid)) {
  251.             foreach ($valid as $valid_code) {
  252.                 if ($this->_code === $valid_code) {
  253.                     return true;
  254.                 }
  255.             }
  256.         }
  257.  
  258.         return new PEAR_Error("Invalid response code received from server");
  259.     }
  260.  
  261.     /**
  262.      * Return a 2-tuple containing the last response from the LMTP server.
  263.      *
  264.      * @return  array   A two-element array: the first element contains the
  265.      *                  response code as an integer and the second element
  266.      *                  contains the response's arguments as a string.
  267.      *
  268.      * @access  public
  269.      * @since   1.0
  270.      */
  271.     function getResponse()
  272.     {
  273.         return array($this->_code, join("\n", $this->_arguments));
  274.     }
  275.  
  276.     /**
  277.      * Attempt to connect to the LMTP server.
  278.      *
  279.      * @param   int     $timeout    The timeout value (in seconds) for the
  280.      *                              socket connection.
  281.      *
  282.      * @return mixed Returns a PEAR_Error with an error message on any
  283.      *               kind of failure, or true on success.
  284.      * @access public
  285.      * @since  1.0
  286.      */
  287.     function connect($timeout = null)
  288.     {
  289.         $result = $this->_socket->connect($this->_host, $this->_port,
  290.                                           false, $timeout);
  291.         if (PEAR::isError($result)) {
  292.             return new PEAR_Error('Failed to connect socket: ' .
  293.                                   $result->getMessage());
  294.         }
  295.  
  296.         if (PEAR::isError($error = $this->_parseResponse(220))) {
  297.             return $error;
  298.         }
  299.         if (PEAR::isError($error = $this->_negotiate())) {
  300.             return $error;
  301.         }
  302.  
  303.         return true;
  304.     }
  305.  
  306.     /**
  307.      * Attempt to disconnect from the LMTP server.
  308.      *
  309.      * @return mixed Returns a PEAR_Error with an error message on any
  310.      *               kind of failure, or true on success.
  311.      * @access public
  312.      * @since  1.0
  313.      */
  314.     function disconnect()
  315.     {
  316.         if (PEAR::isError($error = $this->_put('QUIT'))) {
  317.             return $error;
  318.         }
  319.         if (PEAR::isError($error = $this->_parseResponse(221))) {
  320.             return $error;
  321.         }
  322.         if (PEAR::isError($error = $this->_socket->disconnect())) {
  323.             return new PEAR_Error('Failed to disconnect socket: ' .
  324.                                   $error->getMessage());
  325.         }
  326.  
  327.         return true;
  328.     }
  329.  
  330.     /**
  331.      * Attempt to send the LHLO command and obtain a list of ESMTP
  332.      * extensions available
  333.      *
  334.      * @return mixed Returns a PEAR_Error with an error message on any
  335.      *               kind of failure, or true on success.
  336.      *
  337.      * @access private
  338.      * @since  1.0
  339.      */
  340.     function _negotiate()
  341.     {
  342.         if (PEAR::isError($error = $this->_put('LHLO', $this->_localhost))) {
  343.             return $error;
  344.         }
  345.  
  346.         if (PEAR::isError($this->_parseResponse(250))) {
  347.             return new PEAR_Error('LHLO was not accepted: ', $this->_code);
  348.             return true;
  349.         }
  350.  
  351.         foreach ($this->_arguments as $argument) {
  352.             $verb = strtok($argument, ' ');
  353.             $arguments = substr($argument, strlen($verb) + 1,
  354.                                 strlen($argument) - strlen($verb) - 1);
  355.             $this->_esmtp[$verb] = $arguments;
  356.         }
  357.  
  358.         return true;
  359.     }
  360.  
  361.     /**
  362.      * Returns the name of the best authentication method that the server
  363.      * has advertised.
  364.      *
  365.      * @return mixed    Returns a string containing the name of the best
  366.      *                  supported authentication method or a PEAR_Error object
  367.      *                  if a failure condition is encountered.
  368.      * @access private
  369.      * @since  1.0
  370.      */
  371.     function _getBestAuthMethod()
  372.     {
  373.         $available_methods = explode(' ', $this->_esmtp['AUTH']);
  374.  
  375.         foreach ($this->supportedAuthMethods as $method) {
  376.             if (in_array($method, $available_methods)) {
  377.                 return $method;
  378.             }
  379.         }
  380.  
  381.         return new PEAR_Error('No supported authentication methods');
  382.     }
  383.  
  384.     /**
  385.      * Attempt to do LMTP authentication.
  386.      *
  387.      * @param string The userid to authenticate as.
  388.      * @param string The password to authenticate with.
  389.      * @param string The requested authentication method.  If none is
  390.      *               specified, the best supported method will be used.
  391.      *
  392.      * @return mixed Returns a PEAR_Error with an error message on any
  393.      *               kind of failure, or true on success.
  394.      * @access public
  395.      * @since  1.0
  396.      */
  397.     function auth($uid, $pwd , $method = '')
  398.     {
  399.         if (!array_key_exists('AUTH', $this->_esmtp)) {
  400.             return new PEAR_Error('LMTP server does no support authentication');
  401.         }
  402.  
  403.         /*
  404.          * If no method has been specified, get the name of the best supported
  405.          * method advertised by the LMTP server.
  406.          */
  407.         if (empty($method) || $method === true ) {
  408.             if (PEAR::isError($method = $this->_getBestAuthMethod())) {
  409.                 /* Return the PEAR_Error object from _getBestAuthMethod(). */
  410.                 return $method;
  411.             } 
  412.         } else {
  413.             $method = strtoupper($method);
  414.         }
  415.  
  416.         switch ($method) {
  417.             case 'DIGEST-MD5':
  418.                 $result = $this->_authDigest_MD5($uid, $pwd);
  419.                 break;
  420.             case 'CRAM-MD5':
  421.                 $result = $this->_authCRAM_MD5($uid, $pwd);
  422.                 break;
  423.             case 'LOGIN':
  424.                 $result = $this->_authLogin($uid, $pwd);
  425.                 break;
  426.             case 'PLAIN':
  427.                 $result = $this->_authPlain($uid, $pwd);
  428.                 break;
  429.             default : 
  430.                 $result = new PEAR_Error("$method is not a supported authentication method");
  431.                 break;
  432.         }
  433.  
  434.         /* If an error was encountered, return the PEAR_Error object. */
  435.         if (PEAR::isError($result)) {
  436.             return $result;
  437.         }
  438.  
  439.         /* RFC-2554 requires us to re-negotiate ESMTP after an AUTH. */
  440.         if (PEAR::isError($error = $this->_negotiate())) {
  441.             return $error;
  442.         }
  443.  
  444.         return true;
  445.     }
  446.  
  447.     /* Authenticates the user using the DIGEST-MD5 method.
  448.      *
  449.      * @param string The userid to authenticate as.
  450.      * @param string The password to authenticate with.
  451.      *
  452.      * @return mixed Returns a PEAR_Error with an error message on any
  453.      *               kind of failure, or true on success.
  454.      * @access private
  455.      * @since  1.0
  456.      */
  457.     function _authDigest_MD5($uid, $pwd)
  458.     {
  459.  
  460.  
  461.         if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
  462.             return $error;
  463.         }
  464.         if (PEAR::isError($error = $this->_parseResponse(334))) {
  465.             return $error;
  466.         }
  467.  
  468.         $challenge = base64_decode($this->_arguments[0]);
  469.         $digest = &Auth_SASL::factory('digestmd5');
  470.         $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
  471.                                                        $this->_host, "smtp"));
  472.  
  473.         if (PEAR::isError($error = $this->_put($auth_str ))) {
  474.             return $error;
  475.         }
  476.         if (PEAR::isError($error = $this->_parseResponse(334))) {
  477.             return $error;
  478.         }
  479.  
  480.         /*
  481.          * We don't use the protocol's third step because LMTP doesn't allow
  482.          * subsequent authentication, so we just silently ignore it.
  483.          */
  484.         if (PEAR::isError($error = $this->_put(' '))) {
  485.             return $error;
  486.         }
  487.         if (PEAR::isError($error = $this->_parseResponse(235))) {
  488.             return $error;
  489.         }
  490.     }
  491.  
  492.     /* Authenticates the user using the CRAM-MD5 method.
  493.      *
  494.      * @param string The userid to authenticate as.
  495.      * @param string The password to authenticate with.
  496.      *
  497.      * @return mixed Returns a PEAR_Error with an error message on any
  498.      *               kind of failure, or true on success.
  499.      * @access private
  500.      * @since  1.0
  501.      */
  502.     function _authCRAM_MD5($uid, $pwd)
  503.     {
  504.  
  505.  
  506.         if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
  507.             return $error;
  508.         }
  509.         if (PEAR::isError($error = $this->_parseResponse(334))) {
  510.             return $error;
  511.         }
  512.  
  513.         $challenge = base64_decode($this->_arguments[0]);
  514.         $cram = &Auth_SASL::factory('crammd5');
  515.         $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
  516.  
  517.         if (PEAR::isError($error = $this->_put($auth_str))) {
  518.             return $error;
  519.         }
  520.         if (PEAR::isError($error = $this->_parseResponse(235))) {
  521.             return $error;
  522.         }
  523.     }
  524.  
  525.     /**
  526.      * Authenticates the user using the LOGIN method.
  527.      *
  528.      * @param string The userid to authenticate as.
  529.      * @param string The password to authenticate with.
  530.      *
  531.      * @return mixed Returns a PEAR_Error with an error message on any
  532.      *               kind of failure, or true on success.
  533.      * @access private 
  534.      * @since  1.0
  535.      */
  536.     function _authLogin($uid, $pwd)
  537.     {
  538.         if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) { 
  539.             return $error;
  540.         }
  541.         if (PEAR::isError($error = $this->_parseResponse(334))) {
  542.             return $error;
  543.         }
  544.  
  545.         if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
  546.             return $error;
  547.         }
  548.         if (PEAR::isError($error = $this->_parseResponse(334))) {
  549.             return $error;
  550.         }
  551.  
  552.         if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
  553.             return $error;
  554.         }
  555.         if (PEAR::isError($error = $this->_parseResponse(235))) {
  556.             return $error;
  557.         }
  558.  
  559.         return true;
  560.     }
  561.  
  562.     /**
  563.      * Authenticates the user using the PLAIN method.
  564.      *
  565.      * @param string The userid to authenticate as.
  566.      * @param string The password to authenticate with.
  567.      *
  568.      * @return mixed Returns a PEAR_Error with an error message on any
  569.      *               kind of failure, or true on success.
  570.      * @access private 
  571.      * @since  1.0
  572.      */
  573.     function _authPlain($uid, $pwd)
  574.     {
  575.         if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
  576.             return $error;
  577.         }
  578.         if (PEAR::isError($error = $this->_parseResponse(334))) {
  579.             return $error;
  580.         }
  581.  
  582.         $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
  583.  
  584.         if (PEAR::isError($error = $this->_put($auth_str))) {
  585.             return $error;
  586.         }
  587.         if (PEAR::isError($error = $this->_parseResponse(235))) {
  588.             return $error;
  589.         }
  590.  
  591.         return true;
  592.     }
  593.  
  594.     /**
  595.      * Send the MAIL FROM: command.
  596.      *
  597.      * @param string The sender (reverse path) to set.
  598.      *
  599.      * @return mixed Returns a PEAR_Error with an error message on any
  600.      *               kind of failure, or true on success.
  601.      * @access public
  602.      * @since  1.0
  603.      */
  604.     function mailFrom($sender)
  605.     {
  606.         if (PEAR::isError($error = $this->_put('MAIL', "FROM:<$sender>"))) {
  607.             return $error;
  608.         }
  609.         if (PEAR::isError($error = $this->_parseResponse(250))) {
  610.             return $error;
  611.         }
  612.  
  613.         return true;
  614.     }
  615.  
  616.     /**
  617.      * Send the RCPT TO: command.
  618.      *
  619.      * @param string The recipient (forward path) to add.
  620.      *
  621.      * @return mixed Returns a PEAR_Error with an error message on any
  622.      *               kind of failure, or true on success.
  623.      * @access public
  624.      * @since  1.0
  625.      */
  626.     function rcptTo($recipient)
  627.     {
  628.         if (PEAR::isError($error = $this->_put('RCPT', "TO:<$recipient>"))) {
  629.             return $error;
  630.         }
  631.         if (PEAR::isError($error = $this->_parseResponse(array(250, 251)))) {
  632.             return $error;
  633.         }
  634.  
  635.         return true;
  636.     }
  637.  
  638.     /**
  639.      * Send the DATA command.
  640.      *
  641.      * @param string The message body to send.
  642.      *
  643.      * @return mixed Returns a PEAR_Error with an error message on any
  644.      *               kind of failure, or true on success.
  645.      * @access public
  646.      * @since  1.0
  647.      */
  648.     function data($data)
  649.     {
  650.  
  651.         if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
  652.             if (strlen($data) >= $this->_esmtp['SIZE']) {
  653.                 $this->disconnect();
  654.                 return new PEAR_Error('Message size excedes the server limit');
  655.             }
  656.         }
  657.     
  658.  
  659.         /*
  660.          * Change Unix (\n) and Mac (\r) linefeeds into Internet-standard CRLF
  661.          * (\r\n) linefeeds.
  662.          */
  663.         $data = preg_replace("/([^\r]{1})\n/", "\\1\r\n", $data);
  664.         $data = preg_replace("/\n\n/", "\n\r\n", $data);
  665.  
  666.         /*
  667.          * Because a single leading period (.) signifies an end to the data,
  668.          * legitimate leading periods need to be "doubled" (e.g. '..').
  669.          */
  670.         $data = preg_replace("/\n\./", "\n..", $data);
  671.  
  672.         if (PEAR::isError($error = $this->_put('DATA'))) {
  673.             return $error;
  674.         }
  675.         if (PEAR::isError($error = $this->_parseResponse(354))) {
  676.             return $error;
  677.         }
  678.  
  679.         if (PEAR::isError($this->_send($data . "\r\n.\r\n"))) {
  680.             return new PEAR_Error('write to socket failed');
  681.         }
  682.         if (PEAR::isError($error = $this->_parseResponse(250))) {
  683.             return $error;
  684.         }
  685.  
  686.         return true;
  687.     }
  688.  
  689.     /**
  690.      * Send the RSET command.
  691.      *
  692.      * @return mixed Returns a PEAR_Error with an error message on any
  693.      *               kind of failure, or true on success.
  694.      * @access public
  695.      * @since  1.0
  696.      */
  697.     function rset()
  698.     {
  699.         if (PEAR::isError($error = $this->_put('RSET'))) {
  700.             return $error;
  701.         }
  702.         if (PEAR::isError($error = $this->_parseResponse(250))) {
  703.             return $error;
  704.         }
  705.  
  706.         return true;
  707.     }
  708.     /**
  709.      * Send the NOOP command.
  710.      *
  711.      * @return mixed Returns a PEAR_Error with an error message on any
  712.      *               kind of failure, or true on success.
  713.      * @access public
  714.      * @since  1.0
  715.      */
  716.     function noop()
  717.     {
  718.         if (PEAR::isError($error = $this->_put('NOOP'))) {
  719.             return $error;
  720.         }
  721.         if (PEAR::isError($error = $this->_parseResponse(250))) {
  722.             return $error;
  723.         }
  724.  
  725.         return true;
  726.     }
  727. }
  728.  
  729. ?>
  730.